﻿namespace NTLib.Core.UTest.Log
{
    using Core.Log;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Test.Resources;
    using NTLib.Core.Tasks;
    using Core.Configuration;
    using Core.Log.Consumer;
    using Core.Configuration.Attributes;
    using System.Threading;

    /// <summary>
    /// 
    /// </summary>
    [TestClass]
    public class NTLogUTest : BaseTestClass
    {
        #region Fields

        private const string CATEGORIE = "NTLog";

        #endregion

        #region Neasted

        /// <summary>
        /// 
        /// </summary>
        [TypeExportConfiguration(typeof(ILogConsumer))]
        public class StockConsumer : ILogConsumer
        {
            #region Fields

            public List<string> _logString = new List<string>();
            public List<string> _logData = new List<string>();
            public List<string> _logContext = new List<string>();
            private bool _withFilter;

            #endregion

            #region Ctor

            public StockConsumer(bool withFilter)
            {
                this._withFilter = withFilter;
            }

            public StockConsumer()
            {
                this._withFilter = true;
            }

            #endregion

            #region Properties

            public IReadOnlyCollection<Type> FilterTypes
            {
                get
                {
                    return (this._withFilter) ? new List<Type>() { typeof(string) } : null;
                }
            }

            public IReadOnlyCollection<Type> FilterContextTypes
            {
                get
                {
                    return null;
                }
            }
            #endregion

            #region Methods

            public void Log(LogContainer log)
            {
                this._logString.Add(log.ToString());
                if (log.Data != null && log.Data is string)
                    this._logData.Add(log.Data as string);
                if (log.Context != null && log.Context is string)
                    this._logContext.Add(log.Context as string);
            }

            public async Task FlushAsync()
            {
                await Task.Yield();
            }

            #endregion
        }

        #endregion

        #region Methods

        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_AutoConfig()
        {
            var consumers = NTLog.Instance.Consumers;
            Assert.AreEqual(consumers.Count, 1);
            Assert.AreEqual(consumers.First().GetType(), typeof(DebugConsoleConsumer));
        }

        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_LogConsumerMultiThread()
        {
            var timeout = base.CreateTimeout(10);
            string data = "poney";
            string context = "dream";

            // Due to special privilege [assembly: InternalsVisibleTo("name of assembly here")]
            // It's possible to create a specific instance of the NTLog 
            NTLog log = new NTLog();

            ConfigManager.Configure(log);
            StockConsumer consumer = new StockConsumer(false);

            log.RegisterConsumer(consumer);

            log.Record(LogType.Information, data, nameof(NTLogUTest), context);
            log.RecordError(new Exception(), data, nameof(NTLogUTest), context);

            List<Task> waiter = new List<Task>();
            int id = 0;
            for (var i = 0; i < 500; ++i)
            {
                waiter.Add(Task.Run(() =>
                {
                    var idlocal = Interlocked.Increment(ref id);
                    log.RecordError("Error", nameof(NTLogUTest), idlocal);
                    log.RecordInformation("Info", nameof(NTLogUTest), idlocal);
                }
                ));
            }

            Task.WaitAll(waiter.ToArray());

            Task.Delay(TimeSpan.FromSeconds(10)).Wait();

            Assert.AreEqual(consumer._logString.Count, 1002);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 1002);
            Assert.AreEqual(consumer._logData.Count, 1001);
            Assert.AreEqual(consumer._logContext.Count, 2);

            Assert.AreEqual(consumer._logData.First(), data);
            Assert.AreEqual(consumer._logContext.First(), context);
        }

        /// <summary>
        /// Test NTLog with only one message and one consumer.
        /// </summary>
        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_SimpleLogConsumer()
        {
            var timeout = base.CreateTimeout(10);
            string data = "poney";
            string context = "dream";

            // Due to special privilege [assembly: InternalsVisibleTo("name of assembly here")]
            // It's possible to create a specific instance of the NTLog 
            NTLog log = new NTLog();

            StockConsumer consumer = new StockConsumer();

            log.RegisterConsumer(consumer);

            log.Record(LogType.Information, data, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 1);
            Assert.AreEqual(consumer._logData.Count, 1);
            Assert.AreEqual(consumer._logContext.Count, 1);

            Assert.AreEqual(consumer._logData.First(), data);
            Assert.AreEqual(consumer._logContext.First(), context);
        }

        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_SimpleLogConsumerByTypeLevel()
        {
            var timeout = base.CreateTimeout(10);
            string data = "poney";
            string dataW = "poneyW";
            string dataEW = "poneyEW";
            string dataE = "poneyE";
            string context = "context";

            // Due to special privilege [assembly: InternalsVisibleTo("name of assembly here")]
            // It's possible to create a specific instance of the NTLog 
            NTLog log = new NTLog();
            log.LogLevel = LogType.Error | LogType.Warning;

            StockConsumer consumer = new StockConsumer();

            log.RegisterConsumer(consumer);

            log.RecordInformation(data, nameof(NTLogUTest), context);
            log.RecordWarning(dataW, nameof(NTLogUTest), context);
            log.RecordError(dataE, nameof(NTLogUTest), context);

            log.Record(LogType.Error | LogType.Warning, dataEW, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 3);
            Assert.AreEqual(consumer._logData.Count, 3);
            Assert.AreEqual(consumer._logContext.Count, 3);

            Assert.IsTrue(consumer._logData.Contains(dataE));
            Assert.IsTrue(consumer._logData.Contains(dataW));
        }

        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_SimpleLogConsumerByFilter()
        {
            var timeout = base.CreateTimeout(10);
            NTLogUTest data = new NTLogUTest();
            string dataW = "poneyW";
            string dataE = "poneyE";
            string context = "context";

            // Due to special privilege [assembly: InternalsVisibleTo("name of assembly here")]
            // It's possible to create a specific instance of the NTLog 
            NTLog log = new NTLog();

            ConfigManager.Configure(log, null, timeout.Token);
            StockConsumer consumer = new StockConsumer();

            log.RegisterConsumer(consumer);

            log.RecordInformation(data, nameof(NTLogUTest), context);
            log.RecordWarning(dataW, nameof(NTLogUTest), context);
            log.RecordError(dataE, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 2);
            Assert.AreEqual(consumer._logData.Count, 2);
            Assert.AreEqual(consumer._logContext.Count, 2);

            Assert.IsTrue(consumer._logData.Contains(dataE));
            Assert.IsTrue(consumer._logData.Contains(dataW));
        }

        /// <summary>
        /// Test NTLog with only one message and one consumer.
        /// </summary>
        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_SimpleLogConsumerByConfig()
        {
            var timeout = base.CreateTimeout(10);
            string data = "poney";
            string context = "dream";

            // Due to special privilege [assembly: InternalsVisibleTo("name of assembly here")]
            // It's possible to create a specific instance of the NTLog 
            NTLog log = new NTLog();

            // Try to config using the current assembly
            ConfigManager.Configure(log, typeof(NTLogUTest).Assembly, timeout.Token);

            Assert.AreEqual(log.Consumers.Count, 1);

            var consumer = log.Consumers.First() as StockConsumer;
            Assert.IsNotNull(consumer);

            log.Record(LogType.Information, data, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 1);
            Assert.AreEqual(consumer._logData.Count, 1);
            Assert.AreEqual(consumer._logContext.Count, 1);

            Assert.AreEqual(consumer._logData.First(), data);
            Assert.AreEqual(consumer._logContext.First(), context);
        }

        /// <summary>
        /// Test NTLog with add and remove consumer.
        /// </summary>
        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_ConsumersManip()
        {
            var timeout = base.CreateTimeout(10);
            string data = "poney";
            string context = "dream";

            // Due to special privilege [assembly: InternalsVisibleTo("name of assembly here")]
            // It's possible to create a specific instance of the NTLog 
            NTLog log = new NTLog();

            ConfigManager.Configure(log, null, timeout.Token);
            StockConsumer consumer = new StockConsumer();
            StockConsumer consumer2 = new StockConsumer();

            var consumerKey = log.RegisterConsumer(consumer);
            log.RegisterConsumer(consumer2);

            log.Record(LogType.Information, data, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 1);
            Assert.AreEqual(consumer._logData.Count, 1);
            Assert.AreEqual(consumer._logContext.Count, 1);

            Assert.AreEqual(consumer._logData.First(), data);
            Assert.AreEqual(consumer._logContext.First(), context);

            Assert.AreEqual(consumer2._logString.Count, 1);
            Assert.AreEqual(consumer2._logData.Count, 1);
            Assert.AreEqual(consumer2._logContext.Count, 1);

            Assert.AreEqual(consumer2._logData.First(), data);
            Assert.AreEqual(consumer2._logContext.First(), context);

            log.UnRegisterConsumer(consumerKey);

            log.Record(LogType.Information, data, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 1);
            Assert.AreEqual(consumer._logData.Count, 1);
            Assert.AreEqual(consumer._logContext.Count, 1);

            Assert.AreEqual(consumer._logData.First(), data);
            Assert.AreEqual(consumer._logContext.First(), context);

            Assert.AreEqual(consumer2._logString.Count, 2);
            Assert.AreEqual(consumer2._logData.Count, 2);
            Assert.AreEqual(consumer2._logContext.Count, 2);
        }

        /// <summary>
        /// Test NTLog with add and remove consumer bye using the clean method.
        /// </summary>
        [TestCategory(CATEGORIE)]
        [TestMethod]
        public void NTLog_ConsumerManipandClear()
        {
            var timeout = base.CreateTimeout(10);
            string data = "poney";
            string context = "dream";

            // Due to special privilege [assembly: InternalsVisibleTo("name of assembly here")]
            // It's possible to create a specific instance of the NTLog 
            NTLog log = new NTLog();

            ConfigManager.Configure(log, null, timeout.Token);
            StockConsumer consumer = new StockConsumer();
            StockConsumer consumer2 = new StockConsumer();

            log.RegisterConsumer(consumer);
            log.RegisterConsumer(consumer2);

            log.Record(LogType.Information, data, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 1);
            Assert.AreEqual(consumer._logData.Count, 1);
            Assert.AreEqual(consumer._logContext.Count, 1);

            Assert.AreEqual(consumer._logData.First(), data);
            Assert.AreEqual(consumer._logContext.First(), context);

            Assert.AreEqual(consumer2._logString.Count, 1);
            Assert.AreEqual(consumer2._logData.Count, 1);
            Assert.AreEqual(consumer2._logContext.Count, 1);

            Assert.AreEqual(consumer2._logData.First(), data);
            Assert.AreEqual(consumer2._logContext.First(), context);

            log.Clear(true);

            log.Record(LogType.Information, data, nameof(NTLogUTest), context);

            log.FlushAsync().Wait(timeout.Token);

            Assert.AreEqual(consumer._logString.Count, 1);
            Assert.AreEqual(consumer._logData.Count, 1);
            Assert.AreEqual(consumer._logContext.Count, 1);

            Assert.AreEqual(consumer._logData.First(), data);
            Assert.AreEqual(consumer._logContext.First(), context);

            Assert.AreEqual(consumer2._logString.Count, 1);
            Assert.AreEqual(consumer2._logData.Count, 1);
            Assert.AreEqual(consumer2._logContext.Count, 1);
        }

        #endregion
    }
}
